// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.linqingying.gitcode.authentication.ui

import com.intellij.collaboration.messages.CollaborationToolsBundle
import com.intellij.ide.BrowserUtil.browse
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBPasswordField
import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.layout.ComponentPredicate
import com.linqingying.gitcode.api.GitCodeApiRequestExecutor
import com.linqingying.gitcode.api.GitCodeApiRequests
import com.linqingying.gitcode.api.GitCodeServerPath
import com.linqingying.gitcode.authentication.util.GCSecurityUtil
import com.linqingying.gitcode.authentication.util.GCSecurityUtil.buildNewTokenUrl
import com.linqingying.gitcode.coroutineToIndicator
import com.linqingying.gitcode.exceptions.GitCodeAuthenticationException
import com.linqingying.gitcode.exceptions.GitCodeParseException
import com.linqingying.gitcode.i18n.GitCodeBundle.message
import com.linqingying.gitcode.ui.util.DialogValidationUtils.notBlank
import com.linqingying.gitcode.ui.util.Validator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.UnknownHostException
import javax.swing.JComponent
import javax.swing.JTextField
import javax.swing.event.DocumentEvent

internal class GCTokenCredentialsUi(
    private val cs: CoroutineScope,
    private val serverTextField: ExtendableTextField,
    val factory: GitCodeApiRequestExecutor.Factory,
    private val isAccountUnique: UniqueLoginPredicate
) : GCCredentialsUi() {

    private val tokenTextField = JBPasswordField()
    private var fixedLogin: String? = null

    override fun Panel.centerPanel() {
        row(message("credentials.server.field")) { cell(serverTextField).align(AlignX.FILL) }
        row(message("credentials.token.field")) {
            cell(tokenTextField)
                .comment(
                    CollaborationToolsBundle.message(
                        "clone.dialog.insufficient.scopes",
                        GCSecurityUtil.MASTER_SCOPES
                    )
                )
                .align(AlignX.FILL)
                .resizableColumn()
            button(message("credentials.button.generate")) { browseNewTokenUrl() }
                .enabledIf(serverTextField.serverValid)
        }

//    row {
//      addWarningForPersistentCredentials(
//        cs,
//        service<GCAccountManager>().canPersistCredentials,
//        ::panel
//      )
//    }
    }

    private fun browseNewTokenUrl() = browse(buildNewTokenUrl(serverTextField.tryParseServer()!!))

    override fun getPreferredFocusableComponent(): JComponent = tokenTextField

    override fun getValidator(): Validator = { notBlank(tokenTextField, message("login.token.cannot.be.empty")) }

    override suspend fun login(server: GitCodeServerPath): Pair<String, String> =
        withContext(Dispatchers.Main.immediate) {
            val token = tokenTextField.text
            val executor = factory.create(server, token)
            val login = acquireLogin(server, executor, isAccountUnique, fixedLogin)
            login to token
        }

    override fun handleAcquireError(error: Throwable): ValidationInfo =
        when (error) {
            is GitCodeParseException -> ValidationInfo(
                error.message ?: message("credentials.invalid.server.path"),
                serverTextField
            )

            else -> handleError(error)
        }

    override fun setBusy(busy: Boolean) {
        tokenTextField.isEnabled = !busy
    }

    fun setFixedLogin(fixedLogin: String?) {
        this.fixedLogin = fixedLogin
    }

    companion object {
        suspend fun acquireLogin(
            server: GitCodeServerPath,
            executor: GitCodeApiRequestExecutor,
            isAccountUnique: UniqueLoginPredicate,
            fixedLogin: String?
        ): String {
            val details = withContext(Dispatchers.IO) {

                coroutineToIndicator {
                    executor.execute(
                        ProgressManager.getInstance().progressIndicator,
                        GitCodeApiRequests.CurrentUser.get(server)
                    )
                }
            }

            val login = details.login
            if (fixedLogin != null && fixedLogin != login) throw GitCodeAuthenticationException("Token should match username \"$fixedLogin\"")
            if (!isAccountUnique(login, server)) throw LoginNotUniqueException(login)

            return login
        }

        fun handleError(error: Throwable): ValidationInfo =
            when (error) {
                is LoginNotUniqueException -> ValidationInfo(
                    message(
                        "login.account.already.added",
                        error.login
                    )
                ).withOKEnabled()

                is UnknownHostException -> ValidationInfo(message("server.unreachable")).withOKEnabled()
                is GitCodeAuthenticationException -> ValidationInfo(
                    message(
                        "credentials.incorrect",
                        error.message.orEmpty()
                    )
                ).withOKEnabled()

                else -> ValidationInfo(
                    message(
                        "credentials.invalid.auth.data",
                        error.message.orEmpty()
                    )
                ).withOKEnabled()
            }
    }
}

private val JTextField.serverValid: ComponentPredicate
    get() = object : ComponentPredicate() {
        override fun invoke(): Boolean = tryParseServer() != null

        override fun addListener(listener: (Boolean) -> Unit) =
            document.addDocumentListener(object : DocumentAdapter() {
                override fun textChanged(e: DocumentEvent) = listener(tryParseServer() != null)
            })
    }

private fun JTextField.tryParseServer(): GitCodeServerPath? =
    try {
        GitCodeServerPath.from(text.trim())
    } catch (e: GitCodeParseException) {
        null
    }
